﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Xsl;
using System.Xml.XPath;
using System.Runtime.Caching;
using System.Security.Cryptography;
using System.IO;
using System.Xml.Linq;

namespace EcodexIntegrationSample.Utils
{
    class GeneradorSello
    {
        String xsltPathCfdi
        {
            get
            {
                return @"..\..\xslt\cfdi\cadenaoriginal_3_2.xslt";
            }
        }

        String xsltPathRetenciones
        {
            get
            {
                return @"..\..\xslt\retenciones\retenciones.xslt";
            }
        }


        public String xmlSellado(String RFC, String NoCertificado, String xml)
        {
            XDocument xmlFile = XDocument.Parse(xml);
            XNamespace cfdi = "http://www.sat.gob.mx/cfd/3";
            XNamespace retenciones = "http://www.sat.gob.mx/esquemas/retencionpago/1";

            if (xmlFile.Element(cfdi + "Comprobante") != null)
                return xmlSelladoCfdi(RFC, NoCertificado, xml);
            else if (xmlFile.Element(retenciones + "Retenciones") != null)
                return xmlSelladoRetencion(RFC, NoCertificado, xml);
            else
                throw new Exception("Tipo de documento no soportado");                
        }


        public String xmlSelladoCfdi(String RFC, String NoCertificado, String xml)
        {
            var Sello = SelladoOpenSSL.Sella(RFC, NoCertificado, ObtenerCadenaOriginal(xml, xsltPathCfdi));

            XDocument xmlFile = XDocument.Parse(xml);
            XNamespace cfdi = "http://www.sat.gob.mx/cfd/3";

            if (xmlFile.Element(cfdi + "Comprobante").Attribute("sello") != null)
                xmlFile.Element(cfdi + "Comprobante").Attribute("sello").Value = Sello;
            else
                xmlFile.Element(cfdi + "Comprobante").SetAttributeValue("sello", Sello);

            if (xmlFile.Element(cfdi + "Comprobante").Attribute("certificado") != null)
                xmlFile.Element(cfdi + "Comprobante").Attribute("certificado").Value = Certificado.GetCertificado(RFC, NoCertificado);
            else
                xmlFile.Element(cfdi + "Comprobante").SetAttributeValue("certificado", Certificado.GetCertificado(RFC, NoCertificado));
            return xmlFile.ToString();
        }

        public String xmlSelladoRetencion(String RFC, String NoCertificado, String xml)
        {
            var Sello = SelladoOpenSSL.Sella(RFC, NoCertificado, ObtenerCadenaOriginal(xml, xsltPathRetenciones));            
            XDocument xmlFile = XDocument.Parse(xml);
            XNamespace retenciones = "http://www.sat.gob.mx/esquemas/retencionpago/1";

            if (xmlFile.Element(retenciones + "Retenciones").Attribute("Sello") != null)
                xmlFile.Element(retenciones + "Retenciones").Attribute("Sello").Value = Sello;
            else
                xmlFile.Element(retenciones + "Retenciones").SetAttributeValue("Sello", Sello);

            if (xmlFile.Element(retenciones + "Retenciones").Attribute("Cert") != null)
                xmlFile.Element(retenciones + "Retenciones").Attribute("Cert").Value = Certificado.GetCertificado(RFC, NoCertificado);
            else
                xmlFile.Element(retenciones + "Retenciones").SetAttributeValue("Cert", Certificado.GetCertificado(RFC, NoCertificado));
            return xmlFile.ToString();
        }

        public string ObtenerHashCadenaOriginalRetenciones(String xml)
        {

            return GetSHA1(ObtenerCadenaOriginal(xml, xsltPathRetenciones));
        }
        public string ObtenerHashCadenaOriginalCfdi(String xml)
        {
            
            return GetSHA1(ObtenerCadenaOriginal(xml,xsltPathCfdi ));
        }

        string ObtenerCadenaOriginal(String xml, String xsltPath)
        {
            StringBuilder sb = new StringBuilder();            
            ObjectCache cache = MemoryCache.Default; // I used MemoryCache - there are more possibilities
            var absolutexslPath = Path.GetFullPath(xsltPath);
            var xsl = cache[absolutexslPath] as XslCompiledTransform;
            if (xsl == null)
            {
                var objecto = new object();
                lock (objecto)
                {

                    xsl = new XslCompiledTransform();
                    var xsltSettings = new XsltSettings(false, true);
                    xsl.Load(absolutexslPath, xsltSettings, new XmlUrlResolver());
                    var policy = new CacheItemPolicy();
                    List<String> paths = null;
                    // retrieve all the dependent file paths
                    ConstructCacheItemPolicy(absolutexslPath, ref paths);
                    policy.ChangeMonitors.Add(new HostFileChangeMonitor(paths));
                    cache.Set(xsltPath, xsl, policy);
                }
            }

            StringWriter sw = new StringWriter(sb);
            using (StringReader stream = new StringReader(xml))
            {
                XPathDocument xpath = new XPathDocument(stream);
                xsl.Transform(xpath, null, sw);
            }
            return sb.ToString();
        }

        private static void ConstructCacheItemPolicy(string xslFilePath, ref List<string> paths)
        {
            // Add current xslt file path into our result set
            if (paths == null) paths = new List<string>();
            if (!paths.Contains(xslFilePath)) paths.Add(xslFilePath);
            var xmlDocument = new XmlDocument();
            // load xslt file into XmlDocument
            xmlDocument.Load(xslFilePath);
            // Take care of xml namespaces
            var xmlNamespaceManager = new XmlNamespaceManager(xmlDocument.NameTable);
            xmlNamespaceManager.AddNamespace("xsl", "http://www.w3.org/1999/XSL/Transform");
            // Select includes
            var xNL = xmlDocument.SelectNodes("//xsl:include", xmlNamespaceManager);
            if (xNL != null)
                // iterated through the includes and perform recursive iterations to collect everything what is in scope
                foreach (XmlElement xE in xNL)
                {
                    var pathRoot = Path.GetDirectoryName(Path.GetFullPath(xslFilePath));
                    if (pathRoot != null)
                    {
                        var xslIncludeFilePath = Path.Combine(pathRoot, xE.GetAttribute("href"));
                        ConstructCacheItemPolicy(xslIncludeFilePath, ref paths);
                    }
                }
        }

        public static string GetSHA1(string str)
        {

            UTF8Encoding UE = new UTF8Encoding();
            byte[] hashValue;
            byte[] message = UE.GetBytes(str);

            using (SHA1Managed hashString = new SHA1Managed())
            {
                string hex = "";
                hashValue = hashString.ComputeHash(message);
                foreach (byte x in hashValue)
                    hex += String.Format("{0:x2}", x);
                return hex;
            }
        }
    }
}
